﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGinnBPM.Runtime.Messages;
using NLog;
using System.IO;
using System.Net;
using NGinnBPM.Lib.Data;
using NGinnBPM.MessageBus;
using System.Collections;
using System.Collections.Specialized;

namespace NGinnBPM.Services.Utilities
{
    /// <summary>
    /// Default implementation of HTTP client. Makes requests
    /// using System.Net.HttpWebRequest
    /// TODO: add some exception filtering for failing requests.
    /// Retry 'transient' http 500
    /// </summary>
    public class HttpClient : IMessageConsumer<SendHttpRequest>, IHttpClient
    {
        private Logger log = LogManager.GetCurrentClassLogger();
        
        public IMessageBus MessageBus { get; set; }
        public bool ReturnSoapFaults { get; set; }
        public bool LogAllRequests { get; set; }

        #region IMessageConsumer<SendHttpRequest> Members

        public void Handle(SendHttpRequest message)
        {
            try
            {
                SendHttpRequestResponse resp = Execute(message);
                MessageBus.Reply(resp);
            }
            catch (Exception ex)
            {
                log.Error("Error handling {0}: {1}", message, ex);
                throw;
            }
        }

        public SendHttpRequestResponse Execute(SendHttpRequest message)
        {
            try
            {
                SendHttpRequestResponse resp = ExecuteInternal(message);
                return resp;
            }
            catch (WebException ex)
            {
                HttpWebResponse resp = (HttpWebResponse) ex.Response;
                log.Error("Web exception ({0}), url: {1}, response? : {2}, Message: {3}", ex.Status, message.Url, resp != null, ex.Message);
                if (resp != null)
                {
                    SendHttpRequestResponse ret = GetResponse(resp, message);
                    log.Error("Response {0}/{1} text: {2}", message.CorrelationId, message.Url, ret.ResponseText);
                    int st = (int)resp.StatusCode;
                    int[] retryingStatuses = new int[] { 403, 404, 408, 503, 504 };
                    if (retryingStatuses.Contains(st))
                    {
                        log.Error("Http status is {0}. Will retry", resp.StatusCode);
                        throw;
                    }
                    else if (st == 500 && !message.SOAPFaultIsValid)
                    {
                        log.Error("Http status is {0} and not accepting SOAP faults. will retry", resp.StatusCode);
                        throw;
                    }
                    log.Warn("Http error, returning error info. Url {0}, HttpStatus: {1}", message.Url, resp.StatusCode);
                    if (st == 500 && message.SOAPFaultIsValid)
                    {
                        log.Info("500. Internal server error. Checking if SOAP/JSON.");
                        if (resp.ContentLength == 0) throw;
                        if (message.ExpectedFaultString != null && message.ExpectedFaultString.Length > 0)
                        {
                            if (!System.Text.RegularExpressions.Regex.IsMatch(ret.ResponseText, message.ExpectedFaultString))
                            {
                                log.Info("");
                                throw;
                            }
                        }
                        else
                        {
                            if (ret.ResponseText.IndexOf("SOAPFault") < 0) throw;
                        }
                    }
                    else
                    {
                        log.Warn("Here we are - unexpected case. Url {0}, status: {1}", message.Url, resp.StatusCode);
                    }
                    return ret;
                }
                else
                {
                    log.Warn("No response returned");
                }
                throw;
            }
            catch (Exception ex)
            {
                log.Error("Error making http request {0} to {1}: {2}", message.CorrelationId, message.Url, ex);
                throw;
            }
        }

        private SendHttpRequestResponse GetResponse(HttpWebResponse resp, SendHttpRequest msg)
        {
            SendHttpRequestResponse ret = new SendHttpRequestResponse();
            ret.HttpStatus = (int) resp.StatusCode;
            ret.CorrelationId = msg.CorrelationId;
            ret.TargetTaskInstanceId = msg.CorrelationId;
            ret.ResponseHeaders = new Hashtable();
            foreach (string h in resp.Headers)
                ret.ResponseHeaders[h] = resp.Headers[h];

            Encoding respEnc = Encoding.ASCII;
            string enc = resp.Headers[HttpResponseHeader.ContentEncoding];
            if (enc != null) respEnc = Encoding.GetEncoding(enc);
            using (Stream stm = resp.GetResponseStream())
            {
                StreamReader sr = new StreamReader(stm, respEnc);
                ret.ResponseText = sr.ReadToEnd();
            }
            return ret;
        }

        private bool IsSOAPFault(string response)
        {
            return false;
        }

        #endregion



        

        private SendHttpRequestResponse ExecuteInternal(SendHttpRequest srq)
        {
            using (WebClient wc = new WebClient())
            {
                ServicePoint sp = ServicePointManager.FindServicePoint(new Uri(srq.Url));
                sp.Expect100Continue = false;
                wc.Encoding = Encoding.GetEncoding(srq.RequestEncoding);
                if (srq.User != null && srq.Passwd != null)
                {
                    wc.Credentials = new NetworkCredential(srq.User, srq.Passwd);
                }
                if (srq.Headers != null)
                {
                    foreach (string k in srq.Headers.Keys)
                    {
                        wc.Headers.Add(string.Format("{0}:{1}", k, srq.Headers[k]));
                    }
                }
                NameValueCollection nvc = new NameValueCollection();
                if (srq.RequestData != null)
                {
                    foreach (string k in srq.RequestData.Keys)
                        nvc[k] = (string)srq.RequestData[k];
                }
                byte[] resp = null;
                string respText = null;
                if (srq.Method == "GET" || srq.Method == "DELETE")
                {
                    foreach (string k in srq.RequestData.Keys)
                        wc.QueryString.Add(k, Convert.ToString(srq.RequestData[k]));
                    if (srq.GoBinary)
                        resp = wc.DownloadData(srq.Url);
                    else
                        respText = wc.DownloadString(srq.Url);
                }
                else if (srq.Method == "POST" || srq.Method == "PUT")
                {
                    if (srq.RequestBodyText != null && srq.RequestBodyText.Length > 0)
                    {
                        if (srq.GoBinary)
                        {
                            byte[] data = Convert.FromBase64String(srq.RequestBodyText);
                            resp = wc.UploadData(srq.Url, srq.Method, data);
                            //respText = Convert.ToBase64String(resp);
                        }
                        else
                            respText = wc.UploadString(srq.Url, srq.Method, srq.RequestBodyText);
                    }
                    else
                    {
                        resp = wc.UploadValues(srq.Url, srq.Method, nvc);
                    }
                }
                else throw new Exception("Method unsupported: " + srq.Method);
                SendHttpRequestResponse ret = new SendHttpRequestResponse();
                ret.Success = true;
                ret.GoBinary = srq.GoBinary;
                ret.TargetTaskInstanceId = srq.CorrelationId;
                ret.CorrelationId = srq.CorrelationId;
                ret.HttpStatus = 200;
                ret.ResponseHeaders = new Hashtable();
                foreach (string h in wc.ResponseHeaders)
                    ret.ResponseHeaders[h] = wc.ResponseHeaders[h];

                if (respText != null)
                {
                    ret.ResponseText = respText;
                }
                else if (resp != null)
                {
                    if (ret.GoBinary)
                        ret.ResponseText = Convert.ToBase64String(resp);
                    else
                    {
                        Encoding respEnc = Encoding.GetEncoding(srq.RequestEncoding);
                        string enc = wc.ResponseHeaders[HttpResponseHeader.ContentEncoding];
                        if (enc != null) respEnc = Encoding.GetEncoding(enc);
                        ret.ResponseText = respEnc.GetString(resp);
                    }
                }
                if (LogAllRequests)
                {
                    log.Info("Request {0} ({1}) Response text: {2}", ret.CorrelationId, srq.Url, ret.ResponseText);
                }
                return ret;
            }
        }

        private void ExecuteRequest(SendHttpRequest srq)
        {
            
            StringBuilder surl = new StringBuilder(srq.Url);
            if (srq.Method == "GET" || srq.Method == "DELETE")
            {
                foreach (string k in srq.RequestData.Keys)
                {
                    surl.Append(string.Format("&{0}={1}", k, srq.RequestData[k]));
                }
            }
            log.Debug("Making request to {0}", surl.ToString());
            HttpWebRequest wrq = (HttpWebRequest)WebRequest.Create(surl.ToString());
            wrq.Method = srq.Method;
            if (srq.User != null && srq.Passwd != null)
            {
                wrq.Credentials = new NetworkCredential(srq.User, srq.Passwd);
            }
            if (srq.Headers != null)
            {
                foreach(string k in srq.Headers.Keys)
                {
                    wrq.Headers.Add(string.Format("{0}:{1}", k, srq.Headers[k]));
                }
            }
            if (srq.Method == "POST" || srq.Method == "PUT")
            {
                if (srq.RequestBodyText != null && srq.RequestBodyText.Length > 0)
                {
                    using (Stream stm = wrq.GetRequestStream())
                    {
                        StreamWriter sw = new StreamWriter(stm, Encoding.GetEncoding(srq.RequestEncoding));
                        sw.Write(srq.RequestBodyText);
                    }
                }
                else
                {
                    PostFormData(srq, wrq);
                }
            }
        }

        private void PostFormData(SendHttpRequest srq, HttpWebRequest wrq)
        {
            StringBuilder postData = new StringBuilder();
            foreach (string key in srq.RequestData.Keys)
            {
                if (postData.Length > 0) postData.Append("&");
                postData.AppendFormat("{0}={1}", key, srq.RequestData[key]);
            }
            log.Debug("Sending data: {0}", postData.ToString());
            byte[] data = Encoding.ASCII.GetBytes(postData.ToString());
            wrq.ContentType = "application/x-www-form-urlencoded";
            wrq.ContentLength = data.Length;
            using (Stream stm = wrq.GetRequestStream())
            {
                stm.Write(data, 0, data.Length);
            }
        }

        
    }
}
